/* Software Name : AsmDex
 * Version : 1.0
 *
 * Copyright © 2012 France Télécom
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the copyright holders nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.ow2.asmdex.util;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import org.ow2.asmdex.AnnotationVisitor;
import org.ow2.asmdex.MethodVisitor;
import org.ow2.asmdex.structureCommon.Label;

/**
 * A {@link MethodVisitor} that prints the AsmDex code that generates the Methods it
 * visits.
 * 
 * @author Julien Névo, based on the ASM Framework.
 */
public class AsmDexifierMethodVisitor extends MethodVisitor {

	private final AsmDexPrinter pr;
	
	/**
	 * Map linking a Label to its name.
	 */
	private HashMap<Label, String> labelNames = new HashMap<Label, String>();
	
    /**
     * Constructs a new {@link AsmDexifierMethodVisitor} object.
     * 
     * @param api api level
     * @param nbTabulations number of spaces
     */
    public AsmDexifierMethodVisitor(int api, final int nbTabulations) {
    	super(api);
    	pr = new AsmDexPrinter();
        pr.currentTabulation = nbTabulations;
    }
    
    
	// ------------------------------------------------
	// Visitor Methods.
	// ------------------------------------------------
	
	@Override
	public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
		pr.addTabulation();
		pr.addText("{\n");
		pr.addTabulation();
		pr.addText("\tav0 = mv.visitAnnotation(");
		pr.addConstant(desc, true);
		pr.addBoolean(visible, false);
		pr.addText(");");
		pr.addEOL();
		pr.closeText();
		
		AsmDexifierAnnotationVisitor av = new AsmDexifierAnnotationVisitor(api, 0, pr.currentTabulation + 1);
		pr.addTextToList(av.getTextComponent());
		
		pr.addTabulation();
		pr.addText("}\n");
		pr.closeText();
		
		return av;
	}

	@Override
	public AnnotationVisitor visitAnnotationDefault() {
		pr.addTabulation();
		pr.addText("{\n");
		pr.addTabulation();
		pr.addText("\tav0 = mv.visitAnnotationDefault();");
		pr.addEOL();
		pr.closeText();
		
		AsmDexifierAnnotationVisitor av = new AsmDexifierAnnotationVisitor(api, 0, pr.currentTabulation + 1);
		pr.addTextToList(av.getTextComponent());
		
		pr.addTabulation();
		pr.addText("}\n");
		pr.closeText();
		
		return av;
	}

	@Override
	public void visitArrayLengthInsn(int destinationRegister, int arrayReferenceBearing) {
		pr.addTabulation();
		pr.addText("mv.visitArrayLengthInsn(");
		pr.addNumber(destinationRegister, true);
		pr.addNumber(arrayReferenceBearing, false);
		pr.addText(");");
		pr.addEOL();
		pr.closeText();
	}

	@Override
	public void visitArrayOperationInsn(int opcode, int valueRegister,
			int arrayRegister, int indexRegister) {
		pr.addTabulation();
		pr.addText("mv.visitArrayOperationInsn(");
		pr.addOpcode(opcode, true);
		pr.addNumber(valueRegister, true);
		pr.addNumber(arrayRegister, true);
		pr.addNumber(indexRegister, false);
		pr.addText(");");
		pr.addEOL();
		pr.closeText();
	}

	@Override
	public void visitAttribute(Object attr) {
		// Ignored by AsmDex.
	}

	@Override
	public void visitCode() {
		pr.addTabulation();
		pr.addText("mv.visitCode();\n");
		pr.closeText();
	}

	@Override
	public void visitEnd() {
		pr.addTabulation();
		pr.addText("mv.visitEnd();\n");
		pr.closeText();
	}

	@Override
	public void visitFieldInsn(int opcode, String owner, String name,
			String desc, int valueRegister, int objectRegister) {
		pr.addTabulation();
		pr.addText("mv.visitFieldInsn(");
		pr.addOpcode(opcode, true);
		pr.addConstant(owner, true);
		pr.addConstant(name, true);
		pr.addConstant(desc, true);
		pr.addNumber(valueRegister, true);
		pr.addNumber(objectRegister, false);
		pr.addText(");");
		pr.addEOL();
		pr.closeText();
	}

	@Override
	public void visitFillArrayDataInsn(int arrayReference, Object[] arrayData) {
		pr.addTabulation();
		pr.addText("mv.visitFillArrayDataInsn(");
		pr.addNumber(arrayReference, true);
		pr.addConstant(arrayData, false);
		pr.addText(");");
		pr.addEOL();
		pr.closeText();
	}

	@Override
	public void visitFrame(int type, int nLocal, Object[] local, int nStack,
			Object[] stack) {
		// Ignored by AsmDex.
	}

	@Override
	public void visitInsn(int opcode) {
		pr.addTabulation();
		pr.addText("mv.visitInsn(");
		pr.addOpcode(opcode, false);
		pr.addText(");");
		pr.addEOL();
		pr.closeText();
	}

	@Override
	public void visitIntInsn(int opcode, int register) {
		pr.addTabulation();
		pr.addText("mv.visitIntInsn(");
		pr.addOpcode(opcode, true);
		pr.addNumber(register, false);
		pr.addText(");");
		pr.addEOL();
		pr.closeText();
	}

	@Override
	public void visitJumpInsn(int opcode, Label label, int registerA,
			int registerB) {
		String name = declareLabel(label);
		pr.addTabulation();
		pr.addText("mv.visitJumpInsn(");
		pr.addOpcode(opcode, true);
		pr.addText(name, true);
		pr.addNumber(registerA, true);
		pr.addNumber(registerB, false);
		pr.addText(");");
		pr.addEOL();
		pr.closeText();
	}

	@Override
	public void visitLabel(Label label) {
		String name = declareLabel(label);
		pr.addTabulation();
		pr.addText("mv.visitLabel(" + name + ");\n");
		pr.closeText();
	}

	@Override
	public void visitLineNumber(int line, Label start) {
		String name = declareLabel(start);
		pr.addTabulation();
		pr.addText("mv.visitLineNumber(");
		pr.addNumber(line, true);
		pr.addText(name + ");");
		pr.addEOL();
		pr.closeText();
	}

	@Override
	public void visitLocalVariable(String name, String desc, String signature,
			Label start, Label end, int index) {
		String nameStart = declareLabel(start);
		String nameEnd = declareLabel(end);
		pr.addTabulation();
		pr.addText("mv.visitLocalVariable(");
		pr.addConstant(name, true);
		pr.addConstant(desc, true);
		pr.addConstant(signature, true);
		pr.addText(nameStart, true);
		pr.addText(nameEnd, true);
		pr.addNumber(index, false);
		pr.addText(");");
		pr.addEOL();
		pr.closeText();
	}

	@Override
	public void visitLocalVariable(String name, String desc, String signature,
			Label start, List<Label> ends, List<Label> restarts, int index) {
		String nameStart = declareLabel(start);
		List<String> nameEnds = declareLabelsToList(ends);
		List<String> nameRestarts = declareLabelsToList(restarts);
		pr.addTabulation();
		pr.addText("mv.visitLocalVariable(");
		pr.addConstant(name, true);
		pr.addConstant(desc, true);
		pr.addConstant(signature, true);
		pr.addText(nameStart, true);
		pr.addStringArrayList(nameEnds, true);
		pr.addStringArrayList(nameRestarts, true);
		pr.addNumber(index, false);
		pr.addText(");");
		pr.addEOL();
		pr.closeText();
	}

	@Override
	public void visitLookupSwitchInsn(int register, Label dflt, int[] keys,
			Label[] labels) {
		String nameDefault = declareLabel(dflt);
		String[] nameLabels = declareLabels(labels);
		pr.addTabulation();
		pr.addText("mv.visitLookupSwitchInsn(");
		pr.addNumber(register, true);
		pr.addText(nameDefault, true);
		pr.addConstant(keys, true);
		pr.addLabelArray(nameLabels, false);
		pr.addText(");");
		pr.addEOL();
		pr.closeText();
	}

	@Override
	public void visitMaxs(int maxStack, int maxLocals) {
		pr.addTabulation();
		pr.addText("mv.visitMaxs(");
		pr.addNumber(maxStack, true);
		pr.addNumber(maxLocals, false);
		pr.addText(");");
		pr.addEOL();
		pr.closeText();
	}

	@Override
	public void visitMethodInsn(int opcode, String owner, String name,
			String desc, int[] arguments) {
		pr.addTabulation();
		pr.addText("mv.visitMethodInsn(");
		pr.addOpcode(opcode, true);
		pr.addConstant(owner, true);
		pr.addConstant(name, true);
		pr.addConstant(desc, true);
		pr.addConstant(arguments, false);
		pr.addText(");");
		pr.addEOL();
		pr.closeText();
	}

	@Override
	public void visitMultiANewArrayInsn(String desc, int[] registers) {
		pr.addTabulation();
		pr.addText("mv.visitMultiANewArrayInsn(");
		pr.addConstant(desc, true);
		pr.addConstant(registers, false);
		pr.addText(");");
		pr.addEOL();
		pr.closeText();
	}

	@Override
	public void visitOperationInsn(int opcode, int destinationRegister,
			int firstSourceRegister, int secondSourceRegister, int value) {
		pr.addTabulation();
		pr.addText("mv.visitOperationInsn(");
		pr.addOpcode(opcode, true);
		pr.addNumber(destinationRegister, true);
		pr.addNumber(firstSourceRegister, true);
		pr.addNumber(secondSourceRegister, true);
		pr.addNumber(value, false);
		pr.addText(");");
		pr.addEOL();
		pr.closeText();
	}

	@Override
	public AnnotationVisitor visitParameterAnnotation(int parameter,
			String desc, boolean visible) {
		pr.addTabulation();
		pr.addText("{\n");
		pr.addTabulation();
		pr.addText("\tav0 = mv.visitParameterAnnotation(");
		pr.addNumber(parameter, true);
		pr.addConstant(desc, true);
		pr.addBoolean(visible, false);
		pr.addText(");");
		pr.addEOL();
		pr.closeText();
		
		AsmDexifierAnnotationVisitor av = new AsmDexifierAnnotationVisitor(api, 0, pr.currentTabulation + 1);
		pr.addTextToList(av.getTextComponent());
		
		pr.addTabulation();
		pr.addText("}\n");
		pr.closeText();
		
		return av;
	}

	@Override
	public void visitParameters(String[] parameters) {
		pr.addTabulation();
		pr.addText("mv.visitParameters(");
		pr.addConstant(parameters, false);
		pr.addText(");");
		pr.addEOL();
		pr.closeText();
	}

	@Override
	public void visitStringInsn(int opcode, int destinationRegister, String string) {
		pr.addTabulation();
		pr.addText("mv.visitStringInsn(");
		pr.addOpcode(opcode, true);
		pr.addNumber(destinationRegister, true);
		pr.addConstant(string, false);
		pr.addText(");");
		pr.addEOL();
		pr.closeText();
	}

	@Override
	public void visitTableSwitchInsn(int register, int min, int max,
			Label dflt, Label[] labels) {
		String nameDefault = declareLabel(dflt);
		String[] nameLabels = declareLabels(labels);
		pr.addTabulation();
		pr.addText("mv.visitTableSwitchInsn(");
		pr.addNumber(register, true);
		pr.addNumber(min, true);
		pr.addNumber(max, true);
		pr.addText(nameDefault, true);
		pr.addLabelArray(nameLabels, false);
		pr.addText(");");
		pr.addEOL();
		pr.closeText();
	}

	@Override
	public void visitTryCatchBlock(Label start, Label end, Label handler,
			String type) {
		String nameStart = declareLabel(start);
		String nameEnd = declareLabel(end);
		String nameHandler = declareLabel(handler);
		pr.addTabulation();
		pr.addText("mv.visitTryCatchBlock(");
		pr.addText(nameStart, true);
		pr.addText(nameEnd, true);
		pr.addText(nameHandler, true);
		pr.addConstant(type, false);
		pr.addText(");");
		pr.addEOL();
		pr.closeText();
	}

	@Override
	public void visitTypeInsn(int opcode, int destinationRegister,
			int referenceBearingRegister, int sizeRegister, String type) {
		pr.addTabulation();
		pr.addText("mv.visitTypeInsn(");
		pr.addOpcode(opcode, true);
		pr.addNumber(destinationRegister, true);
		pr.addNumber(referenceBearingRegister, true);
		pr.addNumber(sizeRegister, true);
		pr.addConstant(type, false);
		pr.addText(");");
		pr.addEOL();
		pr.closeText();
	}

	@Override
	public void visitVarInsn(int opcode, int destinationRegister, int var) {
		pr.addTabulation();
		pr.addText("mv.visitVarInsn(");
		pr.addOpcode(opcode, true);
		pr.addNumber(destinationRegister, true);
		pr.addNumber(var, false);
		pr.addText(");");
		pr.addEOL();
		pr.closeText();
	}

	@Override
	public void visitVarInsn(int opcode, int destinationRegister, long var) {
		pr.addTabulation();
		pr.addText("mv.visitVarInsn(");
		pr.addOpcode(opcode, true);
		pr.addNumber(destinationRegister, true);
		pr.addNumber(var, false);
		pr.addText(");");
		pr.addEOL();
		pr.closeText();
	}

	
	// ------------------------------------------------
	// Private Methods.
	// ------------------------------------------------
	
	/**
     * Appends a declaration of the given label to current text. This
     * declaration is of the form "Label lXXX = new Label();". Does nothing if
     * the given label has already been declared, or if the Label is Null.
     * 
     * @param label a Label. May be Null.
     * @return the name of the Label, whether it existed or had been just created.
     */
    private String declareLabel(final Label label) {
    	String name = null;
    	if (label != null) {
	    	if (!labelNames.containsKey(label)) {
	    		pr.addTabulation();
	            name = "l" + labelNames.size();
	            labelNames.put(label, name);
	            pr.addText("Label " + name + " = new Label();\n");
	    	} else {
	    		name = (String)labelNames.get(label);
	    	}
    	}
    	
    	return name;
    }
    
    /**
     * Appends a declaration of the given label to current text. This
     * declaration is of the form "Label lXXX = new Label();". Does nothing if
     * the given label has already been declared.
     * 
     * @param labels the Labels.
     * @return the name of the Labels, whether it existed or had been just created.
     */
    private String[] declareLabels(final Label[] labels) {
    	String[] names = new String[labels.length];
    	
    	int i = 0;
    	for (Label label : labels) {
    		names[i] = declareLabel(label);
    		i++;
    	}
    	
    	return names;
    }
	
    /**
     * Appends a declaration of the given label to current text. This
     * declaration is of the form "Label lXXX = new Label();". Does nothing if
     * the given label has already been declared.
     * May be Null.
     * 
     * @param labels the Labels, or Null.
     * @return the name of the Labels, whether it existed or had been just created, or Null.
     */
    private List<String> declareLabelsToList(final List<Label> labels) {
    	List<String> names = null;
    	if (labels != null) {
    		names = new ArrayList<String>(labels.size());
	    	for (Label label : labels) {
	    		names.add(declareLabel(label));
	    	}
    	}
    	return names;
    }

	/**
	 * Returns the Text Component this element holds.
	 * @return the Text Component this element holds.
	 */
	public TextComponent getTextComponent() {
		return pr.getTextComponent();
	}
}
